今天我們來寫code操作前幾天設定好的AWS SSM,SSM可以用來安全的儲存一些機密的參數,我們在部屬上AWS Lambda後,像是Line的Access Token和Secret這類參數都會存放在裡面,而本機端開發方便的存放在環境變數裡就好。
那接著我們就一步步來寫操作SSM的function以及對應的Unit Test吧~
一樣我們先開一個資料夾專門處理ssm,並且建立ssm.go以及對應的ssm_test.go,還有一個config.json用來mock參數的
config.json,填入我們可能會存的參數名稱,這邊以Secret和AccessToken為例
{
    "MockChannelSecret":"secret-name",
    "MockChannelAccessToken":"token-name"
}
接著ssm.go,我們一樣建立一個SSM的結構,會存放一個操作SSM的Client
type SSM struct {
    Client *ssm.Client
}
func NewSSM() *SSM {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        panic(err)
    }
    client := ssm.NewFromConfig(cfg)
    return &SSM{
        Client: client,
    }
}
這邊我們定義一個interface SSMGetParameterAPI,並且它裡面定義了GetParameter的方法,GetParameter是用來從SSM拿取參數的方法,我們將他定義在interface裡面的話,可以在測試的時候使用SSMGetParameterAPI模擬AWS取SSM參數的動作。
type SSMGetParameterAPI interface {
    GetParameter(ctx context.Context,
        params *ssm.GetParameterInput,
        optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
}
func (s *SSM) FindParameter(c context.Context, api SSMGetParameterAPI, name string) (string, error) {
    input := &ssm.GetParameterInput{
        Name: &name,
    }
    results, err := api.GetParameter(c, input)
    if err != nil {
        fmt.Println(err.Error())
        return "", err
    }
    fmt.Println(*results.Parameter.Value)
    return *results.Parameter.Value, nil
}
完整的ssm.go檔案如下
package ssm
import (
    "context"
    "fmt"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/ssm"
)
type SSMGetParameterAPI interface {
    GetParameter(ctx context.Context,
        params *ssm.GetParameterInput,
        optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error)
}
func (s *SSM) FindParameter(c context.Context, api SSMGetParameterAPI, name string) (string, error) {
    input := &ssm.GetParameterInput{
        Name: &name,
    }
    results, err := api.GetParameter(c, input)
    if err != nil {
        fmt.Println(err.Error())
        return "", err
    }
    fmt.Println(*results.Parameter.Value)
    return *results.Parameter.Value, nil
}
type SSM struct {
    Client *ssm.Client
}
func NewSSM() *SSM {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        panic(err)
    }
    client := ssm.NewFromConfig(cfg)
    return &SSM{
        Client: client,
    }
}
接著打開ssm_test.go,寫好TestMain,New一個共用的testSSM
var testSSM *SSM
func TestMain(m *testing.M) {
    testSSM = NewSSM()
    os.Exit(m.Run())
}
我們先建立一個struct來implement剛剛的SSMGetParameterAPI,我們自己實現一個GetParameter,讓他依照讀到的參數名稱傳出對應的Parameter Value,來模擬AWS取得SSM參數的功能。
type SSMGetParameterImpl struct{}
func (dt SSMGetParameterImpl) GetParameter(ctx context.Context,
    params *ssm.GetParameterInput,
    optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) {
    var parameter *types.Parameter
    if *params.Name == "secret-name" {
        parameter = &types.Parameter{Value: aws.String("secret-value")}
    }
    if *params.Name == "token-name" {
        parameter = &types.Parameter{Value: aws.String("token-value")}
    }
    output := &ssm.GetParameterOutput{
        Parameter: parameter,
    }
    return output, nil
}
解析config.json,取出json存放對應的參數名稱
type Config struct {
    MockChannelSecret      string `json:"MockChannelSecret"`
    MockChannelAccessToken string `json:"MockChannelAccessToken"`
}
var configFileName = "config.json"
var globalConfig Config
func populateConfiguration(t *testing.T) error {
    content, err := os.ReadFile(configFileName)
    if err != nil {
        return err
    }
    text := string(content)
    err = json.Unmarshal([]byte(text), &globalConfig)
    if err != nil {
        return err
    }
    if globalConfig.MockChannelSecret == "" {
        msg := "You must supply a value for MockChannelSecret in " + configFileName
        return errors.New(msg)
    }
    if globalConfig.MockChannelAccessToken == "" {
        msg := "You must supply a value for MockChannelAccessToken in " + configFileName
        return errors.New(msg)
    }
    return nil
}
補上測試,實際去調用FindParameter,由於我們放進FindParameter的是api := &SSMGetParameterImpl{},而他的GetParameter已經被我們替換掉了,所以就能取出我們在GetParameter預設要存放的值瞜~
func TestFindParameter(t *testing.T) {
    thisTime := time.Now()
    nowString := thisTime.Format("2006-01-02 15:04:05 Monday")
    t.Log("Starting unit test at " + nowString)
    err := populateConfiguration(t)
    if err != nil {
        t.Fatal(err)
    }
    api := &SSMGetParameterImpl{}
    respSecret, err := testSSM.FindParameter(context.Background(), *api, globalConfig.MockChannelSecret)
    if err != nil {
        t.Log("Got an error ...:")
        t.Log(err)
        return
    }
    t.Log("MockChannelSecret value: " + respSecret)
    respToken, err := testSSM.FindParameter(context.Background(), *api, globalConfig.MockChannelAccessToken)
    if err != nil {
        t.Log("Got an error ...:")
        t.Log(err)
        return
    }
    t.Log("MockChannelAccessToken value: " + respToken)
}
最後測試跑過就會像下面這樣囉~
